﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using JasLib.Threading;

namespace JasLib.Wpf
{
    public static class WpfExtensions
    {
        /*****************************************************************************/
        public static Binding AddPropertyPathBinding<TSource, TProperty>(
            this MultiBinding multiBinding,
            TSource source,
            System.Linq.Expressions.Expression<Func<TSource, TProperty>> propertySelector,
            IValueConverter converter = null)
        {
            string strPath = WpfUtilities.GeneratePropertyPathString(propertySelector);
            var binding = new Binding(strPath)
            {
                Source = source,
            };
            if (converter != null)
                binding.Converter = converter;
            multiBinding.Bindings.Add(binding);
            return binding;
        }

        /*****************************************************************************/
        /// <summary>
        /// Sets the provided binding, or clears the existing one if null is provided.
        /// </summary>
        public static void ApplyBinding(this DependencyObject target, DependencyProperty property, BindingBase binding)
        {
            if (binding == null)
                BindingOperations.ClearBinding(target, property);
            else
                BindingOperations.SetBinding(target, property, binding);
        }

        /**********************************************************************************/
        /// <summary>
        /// Creates a FreezableCollection from the enumerable,
        /// simply casting the object reference if it already is one.
        /// </summary>
        public static FreezableCollection<T> AsFreezableCollection<T>(this IEnumerable<T> source)
             where T : global::System.Windows.DependencyObject
        {
            return (source as FreezableCollection<T>) ?? new FreezableCollection<T>(source);
        }

        /*****************************************************************************/
        /// <summary>
        /// </summary>
        public static void ClearBinding(this DependencyObject target, DependencyProperty dp)
        {
            BindingOperations.ClearBinding(target, dp);
            return;
        }

        /************************************************************************************/
        public static IEnumerable<DependencyObject> EnumAllLogicalAncestors(
            this DependencyObject objRoot,
            bool bIncludeSelf = true)
        {
            for (
                DependencyObject parent = objRoot;
                (parent != null) && (bIncludeSelf || parent != objRoot);
                parent = LogicalTreeHelper.GetParent(parent))
            {
                yield return parent;
            }
        }

        /************************************************************************************/
        public static IEnumerable<DependencyObject> EnumAllLogicalDescendants(this DependencyObject objRoot)
        {
            foreach (DependencyObject obj in LogicalTreeHelper.GetChildren(objRoot).OfType<DependencyObject>())
            {
                yield return obj;
                foreach (DependencyObject child in EnumAllLogicalDescendants(obj))
                    yield return child;
            }

            yield break;
        }

        /************************************************************************************/
        public static IEnumerable<DependencyObject> EnumAllVisualAncestors(
            this DependencyObject objRoot,
            bool bIncludeSelf = true)
        {
            for (
                DependencyObject parent = objRoot;
                (parent != null) && (bIncludeSelf || parent != objRoot);
                parent = VisualTreeHelper.GetParent(parent))
            {
                yield return parent;
            }
        }

        /************************************************************************************/
        public static IEnumerable<DependencyObject> EnumAllVisualDescendants(this DependencyObject objRoot)
        {
            int iCount = VisualTreeHelper.GetChildrenCount(objRoot);
            for (int iIndex = 0; iIndex < iCount; iIndex++)
            {
                DependencyObject obj = VisualTreeHelper.GetChild(objRoot, iIndex);
                yield return obj;
                foreach (DependencyObject child in EnumAllVisualDescendants(obj))
                    yield return child;
            }

            yield break;
        }

        /**********************************************************************************/
        public static T FindLogicalParent<T>(this DependencyObject startingObject, bool bIncludeSelf = true)
            where T : DependencyObject
        {
            return EnumAllLogicalAncestors(startingObject, bIncludeSelf).OfType<T>().FirstOrDefault();
        }

        /**********************************************************************************/
        public static T FindVisualParent<T>(this DependencyObject startingObject, bool bIncludeSelf = true)
            where T : DependencyObject
        {
            return EnumAllVisualAncestors(startingObject, bIncludeSelf).OfType<T>().FirstOrDefault();
        }

        /*****************************************************************************/
        public static T GetService<T>(this IServiceProvider serviceProvider)
            where T : class
        {
            return (serviceProvider.GetService(typeof(T)) as T);
        }

        /*****************************************************************************/
        /// <summary>
        /// This is an implementation detail and subject to change. Therefore it should not be used in release builds.
        /// </summary>
        public static bool HasDefaultPropertyValue(this DependencyObject target, DependencyProperty property)
        {
            return (DependencyPropertyHelper.GetValueSource(target, property).BaseValueSource == BaseValueSource.Default);
        }

        /*****************************************************************************/
        /// <summary>
        /// This function is kept private and then only exposed through extensions for the specific elements which are known to implement it.
        /// </summary>
        private static bool PerformInvoke(UIElement element)
        {
            if (element == null || !element.IsEnabled)
                return false;

            AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(element);
            IInvokeProvider provider = (peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider);
            if (provider == null)
                return false;

            provider.Invoke();
            return true;
        }

        /*****************************************************************************/
        /// <summary>
        /// This function is kept private and then only exposed through extensions for the specific elements which are known to implement it.
        /// </summary>
        private static bool PerformToggle(UIElement element)
        {
            if (element == null || !element.IsEnabled)
                return false;

            AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(element);
            IToggleProvider provider = (peer.GetPattern(PatternInterface.Toggle) as IToggleProvider);
            if (provider == null)
                return false;

            provider.Toggle();
            return true;
        }

        /*****************************************************************************/
        public static void PerformClick(this Button button)
        {
            PerformInvoke(button as UIElement);
            return;
        }

        /*****************************************************************************/
        public static void PerformToggle(this CheckBox checkBox)
        {
            PerformToggle(checkBox as UIElement);
            return;
        }

        /*****************************************************************************/
        /// <summary>
        /// FrameworkElement has this instance method but the base DependencyObject does not, for some reason.
        /// </summary>
        public static BindingExpressionBase SetBinding(this DependencyObject element, DependencyProperty dp, BindingBase binding)
        {
            return BindingOperations.SetBinding(element, dp, binding);
        }

        /*****************************************************************************/
        /// <summary>
        /// Sets logical focus on the specified element.
        /// </summary>
        public static void SetFocusedElement(this DependencyObject element, IInputElement childElement)
        {
            FocusManager.SetFocusedElement(element, childElement);

            /// NOTE: This is essentially what .NET Reflector tells us is actually going on:
            //element.SetValue(FocusManager.FocusedElementProperty, childElement);
            return;
        }

        /*****************************************************************************/
        private class ShowWindowAsyncState
        {
            private Window _window = null;
            private EventHandler _closedEventCallback = null;
            private TaskCompletionSource<bool?> _windowLifetimeTask = new TaskCompletionSource<bool?>();

            private ShowWindowAsyncState(Window window)
            {
                if (window == null)
                    throw new ArgumentNullException("window");

                _window = window;
                _closedEventCallback = new EventHandler(this.window_Closed);

                _window.Closed += _closedEventCallback;
                return;
            }

            private void window_Closed(object sender, EventArgs e)
            {
                _window.Closed -= _closedEventCallback;
                _windowLifetimeTask.TrySetResult(_window.DialogResult);
                return;
            }

            public static Task<bool?> ShowAsync(Window window)
            {
                var state = new ShowWindowAsyncState(window);
                window.Show();
                return state._windowLifetimeTask.Task;
            }
        }

        public static Task<bool?> ShowAsync(this Window window)
        {
            /// Window.DialogResult can only be set if the window is running modal via ShowDialog,
            /// so we can only return the Task instead of the Task<bool?>.
            return ShowWindowAsyncState.ShowAsync(window);
        }

        /**********************************************************************************/
        public static Awaitable SwitchToAsync(this Dispatcher dispatcher, DispatcherPriority priority = DispatcherPriority.Send)
        {
            return AsyncThreadSwitcher.SwitchToDispatcherAsync(dispatcher, priority);
        }

        /**********************************************************************************/
        public static Awaitable SwitchToDispatcherAsync(
            this DispatcherObject dispatcherObject,
            DispatcherPriority priority = DispatcherPriority.Send)
        {
            return AsyncThreadSwitcher.SwitchToDispatcherAsync(dispatcherObject.Dispatcher, priority);
        }

    }
}
